package org.finesys.common.netty.websocket.stomp.handler;

import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderNames.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_KEY;
import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_VERSION;
import static io.netty.handler.codec.http.HttpHeaderNames.UPGRADE;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_0;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.CharsetUtil;

@ChannelHandler.Sharable
public class StompWebSocketClientPageHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    public static final StompWebSocketClientPageHandler INSTANCE = new StompWebSocketClientPageHandler();


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest request) throws Exception {
        // 处理HTTP请求
        handleHttpRequest(channelHandlerContext, request);
    }

    /**
     * 处理HTTP请求
     *
     * @param context ChannelHandlerContext，提供了对Channel、ChannelPipeline及其操作的访问权限
     * @param request FullHttpRequest，表示一个完整的HTTP请求
     */
    private void handleHttpRequest(ChannelHandlerContext context, FullHttpRequest request) {
        // 如果不是WebSocket请求，发送错误响应
        if (isWebSocketRequest(request)) {
            context.fireChannelRead(request.retain());
            return;
        }
        // 如果请求解码失败，发送错误响应
        if (!request.decoderResult().isSuccess()) {
            sendErrorResponse(context, request, BAD_REQUEST, null);
            return;
        }

        // 处理WebSocket请求
        if (!sendResource(request, context)) {
            FullHttpResponse notFound = new DefaultFullHttpResponse(request.protocolVersion(), NOT_FOUND);
            notFound.headers().set(CONTENT_TYPE, TEXT_PLAIN);
            String payload = "Requested resource " + request.uri() + " not found";
            notFound.content().writeCharSequence(payload, CharsetUtil.UTF_8);
            HttpUtil.setContentLength(notFound, notFound.content().readableBytes());
            sendResponse(notFound, context, true);
        }
    }

    private static boolean sendResource(FullHttpRequest request, ChannelHandlerContext ctx) {
        if (request.uri().isEmpty() || !request.uri().startsWith("/")) {
            return false;
        }

        String requestResource = request.uri().substring(1);
        if (requestResource.isEmpty()) {
            requestResource = "indexWebSocket.html";
        }

        URL resourceUrl = INSTANCE.getClass().getResource(requestResource);
        if (resourceUrl == null) {
            return false;
        }

        RandomAccessFile raf = null;
        long fileLength = -1L;
        try {
            raf = new RandomAccessFile(resourceUrl.getFile(), "r");
            fileLength = raf.length();
        } catch (FileNotFoundException fne) {
            System.out.println("File not found " + fne.getMessage());
            return false;
        } catch (IOException io) {
            System.out.println("Cannot read file length " + io.getMessage());
            return false;
        } finally {
            if (fileLength < 0 && raf != null) {
                try {
                    raf.close();
                } catch (IOException io) {
                    // Nothing to do
                }
            }
        }

        HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), OK);
        HttpUtil.setContentLength(response, fileLength);

        String contentType = "application/octet-stream";
        if (requestResource.endsWith("html")) {
            contentType = "text/html; charset=UTF-8";
        } else if (requestResource.endsWith("css")) {
            contentType = "text/css; charset=UTF-8";
        } else if (requestResource.endsWith("js")) {
            contentType = "application/javascript";
        }

        response.headers().set(CONTENT_TYPE, contentType);
        sendResponse(response, ctx, false);
        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength));
        ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        return true;
    }

    private static void sendResponse(HttpResponse response, ChannelHandlerContext ctx, boolean autoFlush) {
        if (HttpUtil.isKeepAlive(response)) {
            if (response.protocolVersion().equals(HTTP_1_0)) {
                response.headers().set(CONNECTION, KEEP_ALIVE);
            }
            ctx.write(response);
        } else {
            response.headers().set(CONNECTION, CLOSE);
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
        }

        if (autoFlush) {
            ctx.flush();
        }
    }


    /**
     * 判断请求是否为WebSocket请求
     *
     * @param request 请求对象
     * @return 如果是WebSocket请求则返回true，否则返回false
     */
    private boolean isWebSocketRequest(FullHttpRequest request) {
        // 获取请求头
        HttpHeaders headers = request.headers();
        // 判断请求头中的Upgrade字段是否为"websocket"
        // 并且请求头中的Connection字段是否为"upgrade"
        return HttpHeaderValues.WEBSOCKET.toString().equalsIgnoreCase(headers.get(UPGRADE))
                && HttpHeaderValues.UPGRADE.toString().equalsIgnoreCase(headers.get(CONNECTION))
                && headers.contains(SEC_WEBSOCKET_VERSION)
                && headers.contains(SEC_WEBSOCKET_KEY);
    }

    /**
     * 发送错误响应
     *
     * @param context ChannelHandlerContext，提供了对Channel、ChannelPipeline及其操作的访问权限
     * @param request FullHttpRequest，表示一个完整的HTTP请求
     * @param status  HttpResponseStatus，表示HTTP响应的状态码
     * @param byteBuf ByteBuf，包含要发送的响应内容，可能为null
     *                <p>
     *                如果byteBuf不为null，则创建一个包含byteBuf内容的FullHttpResponse对象，
     *                否则创建一个仅包含状态码的FullHttpResponse对象。
     *                最后，调用sendHttpResponse方法发送响应。
     */
    private void sendErrorResponse(ChannelHandlerContext context, FullHttpRequest request, HttpResponseStatus status, ByteBuf byteBuf) {
        FullHttpResponse response = byteBuf != null
                ? new DefaultFullHttpResponse(HTTP_1_1, status, byteBuf.retainedDuplicate())
                : new DefaultFullHttpResponse(HTTP_1_1, status);
        sendHttpResponse(context, request, response);
    }


    /**
     * 发送HTTP响应
     *
     * @param ctx 通道处理器上下文
     * @param req HTTP请求对象
     * @param res HTTP响应对象
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
        // 如果响应getStatus代码不正常（200） 则生成错误页面
        int statusCode = res.status().code();
        if (statusCode != OK.code() && res.content().readableBytes() == 0) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }

        HttpUtil.setContentLength(res, res.content().readableBytes());
        // 发送响应并在必要时关闭连接
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(res);
        if (!HttpUtil.isKeepAlive(req) || statusCode != OK.code()) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }

    }

}
